/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			Win32OS.d
	Description:	windows 32bit routines' class
	Date:			11.08.2008 by Digited
*/
module moonglide.os.win32.Win32OS;

version( Windows )
{


import
	moonglide.os.IOS,

	tango.sys.win32.Macros,
	tango.sys.win32.UserGdi,

	moonglide.core.Controllers,
	moonglide.util.MoonUtil;

private
{
	bool 		g_running,		/// global flag for windowproc
				g_centerCursor,
				g_fullscreen;

	CWin32OS	p_os;			/// global pointer to win32os module singleton

	Size2ui		g_dimensions;

	CEvent		_event;
	HWND		g_hwnd = null;
}

static this()
{
	_event = CEvent();
}

//===============================================================
/// windows callback function, provides all system input via delegate
//===============================================================
extern(Windows) int WindowProc( HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam )
{
	switch( uMsg )
	{
		case WM_MOUSEMOVE:
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.msmove;

			static bool skipSecondMousemoveMessage = false;

			if( g_centerCursor )
				if( skipSecondMousemoveMessage )
				{
					skipSecondMousemoveMessage = false;
					return 0;
				}

			static POINT lastpnt, newpnt, convpnt;

			if( !g_centerCursor)
				lastpnt = newpnt;

			newpnt.x = LOWORD(lParam);
			newpnt.y = HIWORD(lParam);

//			if( !g_fullscreen )
//				ScreenToClient( g_hwnd, &newpnt );

			_event.ints[ "ms_x" ] = newpnt.x - lastpnt.x;
			_event.ints[ "ms_y" ] = newpnt.y - lastpnt.y;
			p_os.sendEvent( _event );

			if( g_centerCursor )
			{
				convpnt = lastpnt;
//				ClientToScreen( g_hwnd, &convpnt );
				ClientToScreen( g_hwnd, &convpnt );

				skipSecondMousemoveMessage = true;
				SetCursorPos( convpnt.x, convpnt.y );
			}

			return 0;

		case WM_MOUSEWHEEL:
			//_event.ClearData();
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.mswheel;
			_event.ints[ "ms_wheel" ] = HIWORD(wParam);
			p_os.sendEvent( _event );
			return 0;

		case WM_LBUTTONDOWN:
			//_event.ClearData();
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.msbutton;
			_event.ints[ "ms_button" ] = 0;
			_event.bools[ "ms_pressed" ] = true;
			p_os.sendEvent( _event );
			return 0;

		case WM_LBUTTONUP:
			//_event.ClearData();
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.msbutton;
			_event.ints[ "ms_button" ] = 0;
			_event.bools[ "ms_pressed" ] = false;
			p_os.sendEvent( _event );
			return 0;

		case WM_RBUTTONDOWN:
			//_event.ClearData();
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.msbutton;
			_event.ints[ "ms_button" ] = 2;
			_event.bools[ "ms_pressed" ] = true;
			p_os.sendEvent( _event );
			return 0;

		case WM_RBUTTONUP:
			//_event.ClearData();
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.msbutton;
			_event.ints[ "ms_button" ] = 2;
			_event.bools[ "ms_pressed" ] = false;
			p_os.sendEvent( _event );
			return 0;

		case WM_MBUTTONDOWN:
			//_event.ClearData();
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.msbutton;
			_event.ints[ "ms_button" ] = 1;
			_event.bools[ "ms_pressed" ] = true;
			p_os.sendEvent( _event );
			return 0;

		case WM_MBUTTONUP:
			//_event.ClearData();
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.msbutton;
			_event.ints[ "ms_button" ] = 1;
			_event.bools[ "ms_pressed" ] = false;
			p_os.sendEvent( _event );
			return 0;

		case WM_KEYDOWN:
			//_event.ClearData();
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.kbkey;
			_event.ints[ "kb_key" ] = wParam;
			_event.bools[ "kb_pressed" ] = true;
			p_os.sendEvent( _event );
			return 0;

		case WM_KEYUP:
			//_event.ClearData();
			_event.type = eeventtype.system;
			_event.subtype = esystemevent.kbkey;
			_event.ints[ "kb_key" ] = wParam;
			_event.bools[ "kb_pressed" ] = false;
			p_os.sendEvent( _event );
			return 0;

		case WM_SIZE:
			//_event.ClearData();
			_event.type = eeventtype.program;
			_event.subtype = eprogramevent.resize;
			RECT wr;
			GetClientRect( g_hwnd, &wr );
			_event.ints[ "size_x" ] = wr.right - wr.left;
			_event.ints[ "size_y" ] = wr.bottom - wr.top;
			p_os.sendEvent( _event );
			return 0;

		case WM_CLOSE:
			g_running = false;
			PostQuitMessage(0);
			return 0;

		case WM_DESTROY:
			g_running = false;
			PostQuitMessage(0);
			return 0;

		// Preventing from powersafe mode & screensaver
		case WM_SYSCOMMAND:
			switch( wParam )
			{
				case SC_SCREENSAVE:
				case SC_MONITORPOWER:
					return 0;
				default:
					return DefWindowProcA( hWnd, uMsg, wParam, lParam );
			}
			return 0;

		default:
			return DefWindowProcA( hWnd, uMsg, wParam, lParam );
	}
}


//===============================================================
/// Win32OS - class for working with 32-bit windows operation systems
//===============================================================
class CWin32OS : IOS
{
	mixin TEventSender;

	protected:
		this()
		{
			_id = "Win32OS";
			p_os = this;
		}

		char[]		_windowCaption = "";

		MSG			msg;
		HINSTANCE	_hinst;
		HWND		_hWnd = null;
		bool		_windowcreated,
					_terminated,
					_running;

		static CWin32OS singleton;

	public:
		static CWin32OS opCall()
		{
			if( singleton is null )
				singleton = new CWin32OS;

			return singleton;
		}

		~this() { shutdown(); }

		//===============================================================
		/// Creating a window. Returns true if sucsess
		//===============================================================
		bool createPlatformWindow( inout Size2ui dimensions, bool fullscreen, char[] name )
		{
			if( _windowcreated )
			{
				errorLog( "CWin32OS module: trying to create another window!" );
				return false;
			}

			attentionLogB( "CWin32OS module: Starting window creation" );

			HINSTANCE _hinst	= GetModuleHandleA(null);

			RECT WindowRect;

			WindowRect.left		= 100;
			WindowRect.right	= dimensions.x + 100;
			WindowRect.top		= 100;
			WindowRect.bottom	= dimensions.y + 100;

			WNDCLASSA wca;

			wca.lpszClassName	= "DWndClass";
			wca.style			= CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
			wca.lpfnWndProc		= &WindowProc;
			wca.hInstance		= _hinst;
			wca.hIcon			= null;
			wca.hCursor			= null;//LoadCursor(null, IDC_ARROW);  // disabled with creating our own cursor
			wca.hbrBackground	= null;
			wca.lpszMenuName	= null;
			wca.cbClsExtra		= wca.cbWndExtra = 0;

			if( !RegisterClassA( &wca ) )
				return false;

			uint dwStyle, dwExStyle;

			if( fullscreen )
			{
				dwExStyle = 0x40000;
				dwStyle = WS_POPUP | WS_VISIBLE;
			}
			else
			{
				dwExStyle = 0x40000 |  256;
				dwStyle = WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX |
					WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPEDWINDOW;
			}

			Size2ui externalDims;

			AdjustWindowRectEx( &WindowRect, dwStyle, false, dwExStyle );	// Adjust Window To True Requested Size
			externalDims.x = WindowRect.right - WindowRect.left;
			externalDims.y = WindowRect.bottom - WindowRect.top;

			g_dimensions = dimensions;

			if( fullscreen )
			{
				g_fullscreen = true;
				SetCursorPos( dimensions.x / 2, dimensions.y / 2 );
			}

			_hWnd = CreateWindowExA(
				dwExStyle,
				wca.lpszClassName,
				name.ptr,
				dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
				WindowRect.left, WindowRect.top,
				externalDims.x,
				externalDims.y,
				HWND_DESKTOP,
				cast(HMENU) null,
				_hinst,
				null );

			if( !_hWnd )
				return false;

			// if( hide_system_cursor )
			ShowCursor(false);

			if( fullscreen )
				freezeCursor( true );	// grab mouse cursor completely (release in destroyWindow)

			g_hwnd = _hWnd;

			ShowWindow( _hWnd, SW_SHOW );
			UpdateWindow( _hWnd );

			_windowcreated = true;
			_terminated = false;

			attentionLogG( "window successfully created." );

			return true;
		}

		//===============================================================
		/// returns HWND of the created win32 window
		//===============================================================
		HWND getHandle() { return _hWnd; };

		//===============================================================
		/// subj. Used for fullscreen
		//===============================================================
		bool changeScreenResolution( uint width, uint height, ubyte bpp, bool fullscreen, ushort RefreshFrequency )
		{
			// TODO (Digited#): how can it be that the code here is missing? I remember myself writing it. Very strange. TODO!
			/*
			ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
			ChangeDisplaySettings(NULL, 0);
			*/

            DEVMODE dm;
            ZeroMemory(&dm,dm.sizeof);
            with (dm)
            {
                  dmSize = dm.sizeof;
                  dmPelsWidth = width;
                  dmPelsHeight = height;
                  dmBitsPerPel = bpp;
                  dmFields = DM_BITSPERPEL || DM_PELSWIDTH || DM_PELSHEIGHT;
            }


            ///if (ChangeDisplaySettings(dm ,CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
///              return false;

			return true;
		}

		//============================================================
		/// grab or ungrab mouse cursor
		//============================================================
		void freezeCursor( bool freeze )
		{
			g_centerCursor = freeze;

			if( freeze )
				SetCapture( _hWnd );
			else
				ReleaseCapture();
		}

		//=============================================================
		/// set window caption
		//=============================================================
		void setWindowCaption( char[] caption, bool store = true )
		{
			if( _windowcreated && g_hwnd )
			{
				caption ~= '\0';
				SetWindowTextA( g_hwnd, caption.ptr );

				if( store )
					_windowCaption = caption;
			}
		}

		//=============================================================
		/// get window caption
		//=============================================================
		char[] getWindowCaption()
		{
			return _windowCaption;
		}


		//=============================================================
		/// get asyncronous keyboard key states
		//=============================================================
		void checkKeyStates()
		{
			foreach( i, key; keyboardState.keys )
				keyboardState( i, GetAsyncKeyState( i ) != 0 );
		}

		bool getKeyState( uint index )
		{
			return GetAsyncKeyState( index ) != 0;
		}

		//===============================================================
		///runs system messages' cycle ONCE - use it once per frame.
		//===============================================================
		bool runLoop()
		{
			static MSG msg;

			g_running = true;

			while( PeekMessageA( &msg, null, 0, 0, PM_REMOVE ) )
			{
				if( msg.message == WM_QUIT )
				{
					g_running = false;
				}
				else
				{
					TranslateMessage( &msg );
					DispatchMessageA( &msg );
				}
			}

			return g_running;
		}

		//===============================================================
		/// subj. Also called from destructor, works once per created window.
		//===============================================================
		void shutdown()
		{
			if( _terminated ) return;

			debug debugLogB( "OS module shutdown" );

			_terminated = true;
			_running = false;
			g_running = false;

			ShowCursor(true);

			if( g_fullscreen )
				freezeCursor( false );

			DestroyWindow( _hWnd );
			_hWnd = null;
			UnregisterClassA( "MoonGlideClass", _hinst );
			_hinst =  null;
		}

		//===============================================================
		/// static member function to transform wchar with utf16 symbols to utf8 that can be printed
		/// to console( via putc() ). Windows-dependent.
		//===============================================================
		static char[] U16toU8( wchar[] line )
		{
			static char[] ch;
			ch = new char[ line.length ];
			CharToOemW( line.ptr, ch.ptr );
			return ch.dup;
		}
} // end class CWin32OS

} // end version( Windows )
